1   package org.apache.lucene.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.lang.reflect.Array;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.Modifier;
23  import java.security.AccessController;
24  import java.security.PrivilegedAction;
25  import java.util.AbstractList;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.IdentityHashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  /** Crawls object graph to collect RAM usage for testing */
36  public final class RamUsageTester {
37  
38    /** An accumulator of object references. This class allows for customizing RAM usage estimation. */
39    public static class Accumulator {
40  
41      /** Accumulate transitive references for the provided fields of the given
42       *  object into <code>queue</code> and return the shallow size of this object. */
43      public long accumulateObject(Object o, long shallowSize, Map<Field, Object> fieldValues, Collection<Object> queue) {
44        for (Object value : fieldValues.values()) {
45          queue.add(value);
46        }
47        return shallowSize;
48      }
49  
50      /** Accumulate transitive references for the provided values of the given
51       *  array into <code>queue</code> and return the shallow size of this array. */
52      public long accumulateArray(Object array, long shallowSize, List<Object> values, Collection<Object> queue) {
53        queue.addAll(values);
54        return shallowSize;
55      }
56  
57    }
58  
59    /**
60     * Estimates the RAM usage by the given object. It will
61     * walk the object tree and sum up all referenced objects.
62     *
63     * <p><b>Resource Usage:</b> This method internally uses a set of
64     * every object seen during traversals so it does allocate memory
65     * (it isn't side-effect free). After the method exits, this memory
66     * should be GCed.</p>
67     */
68    public static long sizeOf(Object obj, Accumulator accumulator) {
69      return measureObjectSize(obj, accumulator);
70    }
71  
72    /** Same as calling <code>sizeOf(obj, DEFAULT_FILTER)</code>. */
73    public static long sizeOf(Object obj) {
74      return sizeOf(obj, new Accumulator());
75    }
76  
77    /**
78     * Return a human-readable size of a given object.
79     * @see #sizeOf(Object)
80     * @see RamUsageEstimator#humanReadableUnits(long)
81     */
82    public static String humanSizeOf(Object object) {
83      return RamUsageEstimator.humanReadableUnits(sizeOf(object));
84    }
85  
86    /*
87     * Non-recursive version of object descend. This consumes more memory than recursive in-depth
88     * traversal but prevents stack overflows on long chains of objects
89     * or complex graphs (a max. recursion depth on my machine was ~5000 objects linked in a chain
90     * so not too much).
91     */
92    private static long measureObjectSize(Object root, Accumulator accumulator) {
93      // Objects seen so far.
94      final Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
95      // Class cache with reference Field and precalculated shallow size. 
96      final IdentityHashMap<Class<?>, ClassCache> classCache = new IdentityHashMap<>();
97      // Stack of objects pending traversal. Recursion caused stack overflows. 
98      final ArrayList<Object> stack = new ArrayList<>();
99      stack.add(root);
100 
101     long totalSize = 0;
102     while (!stack.isEmpty()) {
103       final Object ob = stack.remove(stack.size() - 1);
104 
105       if (ob == null || seen.contains(ob)) {
106         continue;
107       }
108       seen.add(ob);
109 
110       final Class<?> obClazz = ob.getClass();
111       assert obClazz != null : "jvm bug detected (Object.getClass() == null). please report this to your vendor";
112       if (obClazz.isArray()) {
113         /*
114          * Consider an array, possibly of primitive types. Push any of its references to
115          * the processing stack and accumulate this array's shallow size. 
116          */
117         final long shallowSize = RamUsageEstimator.shallowSizeOf(ob);
118         final int len = Array.getLength(ob);
119         final List<Object> values;
120         Class<?> componentClazz = obClazz.getComponentType();
121         if (componentClazz.isPrimitive()) {
122           values = Collections.emptyList();
123         } else {
124           values = new AbstractList<Object>() {
125 
126             @Override
127             public Object get(int index) {
128               return Array.get(ob, index);
129             }
130 
131             @Override
132             public int size() {
133               return len;
134               }
135               
136             };         
137           }
138         totalSize += accumulator.accumulateArray(ob, shallowSize, values, stack);
139       } else {
140         /*
141          * Consider an object. Push any references it has to the processing stack
142          * and accumulate this object's shallow size. 
143          */
144         try {
145           ClassCache cachedInfo = classCache.get(obClazz);
146           if (cachedInfo == null) {
147             classCache.put(obClazz, cachedInfo = createCacheEntry(obClazz));
148           }
149 
150           Map<Field, Object> fieldValues = new HashMap<>();
151           for (Field f : cachedInfo.referenceFields) {
152             fieldValues.put(f, f.get(ob));
153           }
154 
155           totalSize += accumulator.accumulateObject(ob, cachedInfo.alignedShallowInstanceSize, fieldValues, stack);
156         } catch (IllegalAccessException e) {
157           // this should never happen as we enabled setAccessible().
158           throw new RuntimeException("Reflective field access failed?", e);
159         }
160       }
161     }
162 
163     // Help the GC (?).
164     seen.clear();
165     stack.clear();
166     classCache.clear();
167 
168     return totalSize;
169   }
170   
171 
172   /**
173    * Cached information about a given class.   
174    */
175   private static final class ClassCache {
176     public final long alignedShallowInstanceSize;
177     public final Field[] referenceFields;
178 
179     public ClassCache(long alignedShallowInstanceSize, Field[] referenceFields) {
180       this.alignedShallowInstanceSize = alignedShallowInstanceSize;
181       this.referenceFields = referenceFields;
182     }    
183   }
184   
185   /**
186    * Create a cached information about shallow size and reference fields for 
187    * a given class.
188    */
189   private static ClassCache createCacheEntry(final Class<?> clazz) {
190     return AccessController.doPrivileged(new PrivilegedAction<ClassCache>() {
191       @Override
192       @SuppressForbidden(reason = "We need to access private fields of measured objects.")
193       public ClassCache run() {
194         ClassCache cachedInfo;
195         long shallowInstanceSize = RamUsageEstimator.NUM_BYTES_OBJECT_HEADER;
196         final ArrayList<Field> referenceFields = new ArrayList<>(32);
197         for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
198           if (c == Class.class) {
199             // prevent inspection of Class' fields, throws SecurityException in Java 9!
200             continue; 
201           }
202           final Field[] fields = c.getDeclaredFields();
203           for (final Field f : fields) {
204             if (!Modifier.isStatic(f.getModifiers())) {
205               shallowInstanceSize = RamUsageEstimator.adjustForField(shallowInstanceSize, f);
206     
207               if (!f.getType().isPrimitive()) {
208                 f.setAccessible(true);
209                 referenceFields.add(f);
210               }
211             }
212           }
213         }
214 
215         cachedInfo = new ClassCache(
216             RamUsageEstimator.alignObjectSize(shallowInstanceSize), 
217             referenceFields.toArray(new Field[referenceFields.size()]));
218         return cachedInfo;
219       }
220     });
221   }
222 
223 }